package com.alibaba.aliper.agent.jmeter;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;

import com.alibaba.aliper.log.ReporterLogImager;
import com.alibaba.aliper.util.Dater;

/**
 * TODO Comment of
 * 
 * @author ���潣
 * 
 */
public class JmeterParser {

	/**
	 * @param args
	 * @throws FileNotFoundException//
	 */
	public static void main(String[] args) throws Exception {
		final  JmeterParser parser = new JmeterParser();
		CmdLineParser cmdLineParser = new CmdLineParser(parser);
		try {
			cmdLineParser.parseArgument(args);
		} catch (CmdLineException e) {
			System.err.println(e.getMessage());
			System.out.println("Usage:");
			cmdLineParser.printUsage(System.out);
			return;
		}
		//如下将会把结果输出到文件系统
		
		parser.parse();
		
	}

	private void prepareToStoreOrSend() {
		this.setListener(new JmeterParserListener() {
			@Override
			public void log(ThreadGroupData sample) {
				if(JmeterParser.this.log == null){
					JmeterParser.this.log = new File(JmeterParser.this.file.getParentFile(),JmeterParser.this.file.getName().substring(0, JmeterParser.this.file.getName().lastIndexOf('.')));
				}
				String response = sample.getName()+".res.log";
				String tps = sample.getName()+".tps.log";
				JmeterParser.this.writeToFile(JmeterParser.this.getResponseLogData(sample),response);
				JmeterParser.this.writeToFile(JmeterParser.this.getTpsLogData(sample),tps);
			}
		});
	}

	public void storeResult() throws IOException {
		//将结果写入文件
		FileOutputStream output = null;
		try {
			output = new FileOutputStream(new File(this.file.getParent(), this.file
					.getName()
					+ ".out"));
			output.write(report(this.map, this.file.getName()).getBytes());
			output.flush();
		} finally {
			if (output != null)
				output.close();
		}
		ReporterLogImager.imager(this.log);
	}

	@Option(name = "-p", usage = "threadPoolSize,used to access  response time.", required = false)
	private int threadPoolSize = 24;
	
	@Option(name = "-t", usage = "taskQueueSize,used to store the task created by reading thread.", required = false)
	private int taskQueueSize = 102400;
	
	@Option(name = "-j", usage = "jmeter log file.", required = true)
	private File file = null;
	public void setJmeterLogFile(File file){
		this.file = file;
	}
	
	@Option(name = "-l", usage = "parser log folder.", required = false)
	private File log = null;
	
	@Option(name = "-i", usage = "interval. Sampling interval in milliseconds ", required = false)
	private int interval = 2000;
	public void setSampleInterval(int interval){
		this.interval = interval;
	}
	
	@Option(name = "-r", usage = "reportInterval. reportInterval interval in milliseconds ", required = false)
	private int reportInterval = -1;
	
	@Option(name = "-u", usage = "useThreadPool. Use Or not use thread pool to parser Jmeter file ",metaVar = "true or false", required = false)
	private String useThreadPool = "false";
	
	@Override
	public String toString() {
		return "poolSize:"+threadPoolSize + "\nfile:"+file.getPath();
	}
	
	/*
	 * 用于提升性能的函数，比java正则表达式快80多倍
	 */
	public static String getProperty(String line,String property){
		if(line == null || property == null)
			return null;
		String value = "";
		if(!property.startsWith(" ")){
			property = " "+property;
		}
		int plength = property.length();
		int index = line.indexOf(property+"=\"") ;
		int sindex = index+2+plength;
		int eindex = line.indexOf('\"',sindex);
		if(index < 0 || eindex < sindex)
			return null;
		value = line.substring(sindex, eindex);
		return value;
	}

	//public static final Pattern linePattern = Pattern
	//		.compile(".*\\st\\s*=\\s*\"([0-9]+)\".*\\sts\\s*=\\s*\"([0-9]+)\".*\\ss\\s*=\\s*\"([^\"]*)\".*\\stn\\s*=\\s*\"([^\"]*)-[0-9]+\"");

	private static String report(AbstractMap<String, ThreadGroupData> map,
			String file) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("[" + Dater.getCNTime(System.currentTimeMillis())
				+ "] reporting for file " + file + "\n");
		for (String test : map.keySet()) {
			ThreadGroupData data = map.get(test);
			buffer.append("  TestGroupName: " + test + "\n");
			buffer.append("    [" + Dater.getCNTime(data.getMaxTime())
					+ "]  FailRate: " + data.getFailedRate() + " %" + "\n");
			buffer.append("    [" + Dater.getCNTime(data.getMaxTime())
					+ "]  MaxRes: " + data.getMaxResponse() + "\n");
			buffer.append("    [" + Dater.getCNTime(data.getMinTime())
					+ "]  MinRes: " + data.getMinResponse() + "\n");
			buffer.append("    [" + Dater.getCNTime(data.getCloserTime())
					+ "]  AvgRes: " + data.getAvgResponse() + "\n");
			buffer.append("    [" + Dater.getCNTime(data.getCloserTime())
					+ "]  AvgTps: " + data.getTPS() + "\n");
		}
		return buffer.toString();
	}

	

	class Task implements Runnable {
		private String line = null;

		public Task(String line) {
			this.line = line;
		}

		public void run() {
		//	Matcher matcher = linePattern.matcher(line);
			ThreadGroupData data = null;
		//	if (matcher.find()) {
			String res = getProperty(line, " t");
			String tim = getProperty(line, " ts");
			String suc = getProperty(line, " s");
			String nam = getProperty(line, " tn");
			if(res != null && tim != null && suc != null && nam != null){
			    long response = Long.parseLong(res);
			    long time = Long.parseLong(tim);
				boolean success = "true".equalsIgnoreCase(suc);
				String threadGroupName = nam;
				int lindex = nam.lastIndexOf('-');
				if(lindex >= 0)
					threadGroupName = nam.substring(0, lindex);

				data = JmeterParser.this.getDataOnCreate(threadGroupName);
				ThreadGroupData sample = data.update(time, success, response);
				if(sample != null){
					JmeterParser.this.log(sample);
				}
			} else {
				//System.out.println("ingore line:"+line);
			}
		//	}
		}
	}

	ConcurrentHashMap<String, ThreadGroupData> map = new ConcurrentHashMap<String, ThreadGroupData>();
	ThreadPoolExecutor executor = null;

	public long parse(long index) throws Exception{
		long startTime = System.currentTimeMillis();
		BufferedReader reader = new BufferedReader(new InputStreamReader(
				new FileInputStream(file)));
		reader.skip(index);
		if("true".equals(this.useThreadPool))
			index += parseUseThreadPool(reader);
		else
			index += parseNotUseThreadPool(reader);
		for(String key:map.keySet()){
			ThreadGroupData sample = map.get(key).pack();
			if(sample != null)
				log(sample);
		}
		String content = "ThreadGroup Reporting:\n";
		content += report(map, file.getName());
		System.out.println(content);
		long endTime = System.currentTimeMillis();
		System.out.println("parse ended ,  spent "+(endTime-startTime)/1000 + " seconds");
		return index;
	}
	
	public void parse() throws Exception {
		this.prepareToStoreOrSend();
		parse(0);
		this.storeResult();
	}
	private long parseNotUseThreadPool(BufferedReader reader) throws IOException {
		long currentTime = System.currentTimeMillis();
		String line = null;
		long parsed = 0;
		while ((line = reader.readLine()) != null) {
			parsed+=line.length();
			new Task(line).run();
			if(this.reportInterval > 0){
				if (System.currentTimeMillis() - currentTime > this.reportInterval) {
					currentTime = System.currentTimeMillis();
					System.out.println(report(map, file.getName()));
				}
			}
		}
		return parsed;
	}

	private long parseUseThreadPool(BufferedReader reader ) throws InterruptedException, IOException {
		executor = new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 1000,
				TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue<Runnable>(taskQueueSize) {
					private static final long serialVersionUID = 1L;
					@Override
					public boolean offer(Runnable o) {
						try {
							return super.offer(o, Long.MAX_VALUE,
									TimeUnit.SECONDS);
						} catch (Exception e) {
							return false;
						}
					}
				});
		
		long currentTime = System.currentTimeMillis();
		
		String line = null;
		long parsed = 0;
		while ((line = reader.readLine()) != null) {
			parsed += line.length();
			executor.submit(new Task(line));
			if(this.reportInterval > 0){
				if (System.currentTimeMillis() - currentTime > this.reportInterval) {
					currentTime = System.currentTimeMillis();
					System.out.println(report(map, file.getName()));
				}
			}
		}
		this.executor.shutdown();
		this.executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
		return parsed;
	}

	private HashMap<String, FileOutputStream> logFiles = new HashMap<String, FileOutputStream>(); 
	public synchronized void log(ThreadGroupData sample) {
		
		if(this.listener != null){
			this.listener.log(sample);
		}
	
	}
	private JmeterParserListener listener = null;
	public void setListener(JmeterParserListener listener){
		this.listener = listener;
	};

	private byte[] getTpsLogData(ThreadGroupData sample) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("["+Dater.getCNTime(sample.getLastParseTime()) + "] tps:("+sample.getCloserTime() + ","+sample.getTPS()+")\n");
		return buffer.toString().getBytes();
	}

	private void writeToFile(byte[] data,
			String fileName) {
		FileOutputStream outfile = null;
		if(logFiles.containsKey(fileName)){
			outfile = logFiles.get(fileName);
		} else {
			try {
				File temp = new File(this.log,fileName);
				temp.getParentFile().mkdirs();
				outfile = new FileOutputStream(temp,false);
				logFiles.put(fileName, outfile);
			} catch (FileNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
		
		try {
			outfile.write(data);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	private byte[] getResponseLogData(ThreadGroupData sample) {
		StringBuffer buffer = new StringBuffer();
		buffer.append("["+Dater.getCNTime(sample.getLastParseTime()) + "] maxResponse:("+sample.getMaxTime() + ","+sample.getMaxResponse()+")\n");
		buffer.append("["+Dater.getCNTime(sample.getLastParseTime()) + "] minResponse:("+sample.getMinTime() + ","+sample.getMinResponse()+")\n");
		buffer.append("["+Dater.getCNTime(sample.getLastParseTime()) + "] avgResponse:("+sample.getCloserTime() + ","+sample.getAvgResponse()+")\n");
		return buffer.toString().getBytes();
	}

	public synchronized ThreadGroupData getDataOnCreate(String threadName) {
		ThreadGroupData data = null;
		if (map.containsKey(threadName)) {
			data = map.get(threadName);
		} else {
			data = new ThreadGroupData(threadName,this.interval);
			map.put(threadName, data);
		}
		return data;
	}

}
